﻿'******************************* File Header *********************************
'* File Name:    WmiDataReader.vb
'* Project:      WmiConnection
'* Copyright (c) Freddy Kuehne
'*
'* Default implementation for a wmi datareader.
'* 
'* This source is subject to the GNU Library General Public License (LGPL).
'* See http://wmiconnection.codeplex.com/license.
'* All other rights reserved.
'*****************************************************************************

Imports System.Globalization

'TODO: Get[Array]-Methods
''' <summary>
''' Used to access the WMI data.
''' </summary>
Public Class WmiDataReader
  Inherits DbDataReader
#Region "Variables"
  ''' <summary>
  ''' DataTable with schema-information for the query.
  ''' </summary>
  Private m_Schema As DataTable
  ''' <summary>
  ''' Pointer for sequential reads.
  ''' </summary>
  Private m_Pointer As Integer
#End Region

#Region "Constructors"
  ''' <summary>
  ''' Creates a new WmiDataReader based on the WMI data.
  ''' </summary>
  ''' <param name="data">WMI data</param>
  ''' <exception cref="ArgumentNullException">Exception occrus, when an empty <paramref name="data"/> parameter is passed</exception>
  Friend Sub New(ByVal data As ManagementObjectCollection)
    If (data Is Nothing) Then Throw New ArgumentNullException("data")

    Me.Data = New List(Of ManagementBaseObject)
    Dim en As ManagementObjectCollection.ManagementObjectEnumerator = data.GetEnumerator
    While en.MoveNext
      Me.Data.Add(en.Current)
    End While
    en.Dispose()
    m_Pointer = -1
  End Sub
#End Region

#Region "Properties"
  ''' <summary>
  ''' Wrapped WMI data.
  ''' </summary>
  Private Property Data As List(Of ManagementBaseObject)
#End Region

#Region "IDataReader"
  ''' <summary>
  ''' <see cref="DbDataReader.Close"/>
  ''' </summary>
  Public Overrides Sub Close()
    If (Data IsNot Nothing) Then
      For Each obj In Data
        obj.Dispose()
      Next
      Data.Clear()
      Data = Nothing
    End If

    If (m_Schema IsNot Nothing) Then
      m_Schema.Dispose()
      m_Schema = Nothing
    End If
  End Sub

  ''' <summary>
  ''' <see cref="DbDataReader.Depth"/>
  ''' </summary>
  ''' <remarks>Always returns 0</remarks>
  Public Overrides ReadOnly Property Depth As Integer
    Get
      Depth = 0
    End Get
  End Property

  ''' <summary>
  ''' <see cref="DbDataReader.GetSchemaTable"/>
  ''' </summary>
  Public Overrides Function GetSchemaTable() As DataTable
    Dim save As Integer = m_Pointer
    Dim pd As PropertyData
    Dim t As Type

    Try
      If (m_Schema Is Nothing) Then
        m_Schema = New DataTable()
        m_Schema.Locale = CultureInfo.InvariantCulture
        m_Schema.Columns.Add("ColumnName", GetType(String))
        m_Schema.Columns.Add("ColumnOrdinal", GetType(Integer))
        m_Schema.Columns.Add("ColumnSize", GetType(Integer))
        m_Schema.Columns.Add("NumericPrecision", GetType(Integer))
        m_Schema.Columns.Add("NumericScale", GetType(Integer))
        m_Schema.Columns.Add("DataType", GetType(Type))
        m_Schema.Columns.Add("ProviderType", GetType(Integer))
        m_Schema.Columns.Add("IsLong", GetType(Boolean))
        m_Schema.Columns.Add("AllowDBNull", GetType(Boolean))
        m_Schema.Columns.Add("IsReadOnly", GetType(Boolean))
        m_Schema.Columns.Add("IsRowVersion", GetType(Boolean))
        m_Schema.Columns.Add("IsUnique", GetType(Boolean))
        m_Schema.Columns.Add("IsKey", GetType(Boolean))
        m_Schema.Columns.Add("IsAutoIncrement", GetType(Boolean))
        m_Schema.Columns.Add("BaseSchemaName", GetType(String))
        m_Schema.Columns.Add("BaseCatalogName", GetType(String))
        m_Schema.Columns.Add("BaseTableName", GetType(String))
        m_Schema.Columns.Add("BaseColumnName", GetType(String))

        m_Pointer = 0
        For lv As Integer = 0 To FieldCount - 1
          pd = GetProperty(lv)
          t = MapCimType(pd.Type)
          If (pd.IsArray) Then t = t.MakeArrayType
          m_Schema.Rows.Add(pd.Name, lv, Integer.MaxValue, 0, 0, t, pd.Type, False, True, False, False, False, False, False, Nothing, Nothing, Nothing, Nothing)
        Next
        m_Schema.AcceptChanges()
      End If
    Finally
      m_Pointer = save
    End Try
    GetSchemaTable = m_Schema
  End Function

  ''' <summary>
  ''' <see cref="DbDataReader.IsClosed"/>
  ''' </summary>
  Public Overrides ReadOnly Property IsClosed As Boolean
    Get
      IsClosed = (Data Is Nothing)
    End Get
  End Property

  ''' <summary>
  ''' <see cref="DbDataReader.NextResult"/>
  ''' </summary>
  ''' <remarks>Always returns False</remarks>
  Public Overrides Function NextResult() As Boolean
    NextResult = False
  End Function

  ''' <summary>
  ''' <see cref="DbDataReader.Read"/>
  ''' </summary>
  Public Overrides Function Read() As Boolean
    m_Pointer += 1
    Read = (m_Pointer < RecordsAffected)
  End Function

  ''' <summary>
  ''' <see cref="DbDataReader.RecordsAffected"/>
  ''' </summary>
  Public Overrides ReadOnly Property RecordsAffected As Integer
    Get
      RecordsAffected = Data.Count
    End Get
  End Property

  ''' <summary>
  ''' <see cref="DbDataReader.FieldCount"/>
  ''' </summary>
  ''' <remarks>
  ''' FieldCount is only accurate, when at least one row is available!
  ''' </remarks>
  Public Overrides ReadOnly Property FieldCount As Integer
    Get
      If (RecordsAffected > 0) Then
        FieldCount = Data(0).Properties.Count
      Else
        FieldCount = 0
      End If
    End Get
  End Property

  ''' <summary>
  ''' <see cref="DbDataReader.GetBoolean"/>
  ''' </summary>
  Public Overrides Function GetBoolean(ByVal i As Integer) As Boolean
    GetBoolean = CType(Item(i), Boolean)
  End Function

  ''' <summary>
  ''' <see cref="DbDataReader.GetByte"/>
  ''' </summary>
  Public Overrides Function GetByte(ByVal i As Integer) As Byte
    GetByte = CType(Item(i), Byte)
  End Function

  ''' <summary>
  ''' <see cref="DbDataReader.GetBytes"/>
  ''' </summary>
  ''' <remarks>Not yet supported!</remarks>
  Public Overrides Function GetBytes(ByVal i As Integer, ByVal fieldOffset As Long, ByVal buffer() As Byte, ByVal bufferoffset As Integer, ByVal length As Integer) As Long
    Throw New NotSupportedException()
  End Function

  ''' <summary>
  ''' <see cref="DbDataReader.GetChar"/>
  ''' </summary>
  Public Overrides Function GetChar(ByVal i As Integer) As Char
    GetChar = CType(Item(i), Char)
  End Function

  ''' <summary>
  ''' <see cref="DbDataReader.GetChars"/>
  ''' </summary>
  ''' <remarks>Not yet supported!</remarks>
  Public Overrides Function GetChars(ByVal i As Integer, ByVal fieldoffset As Long, ByVal buffer() As Char, ByVal bufferoffset As Integer, ByVal length As Integer) As Long
    Throw New NotSupportedException
  End Function

  ''' <summary>
  ''' <see cref="DbDataReader.GetDataTypeName"/>
  ''' </summary>
  Public Overrides Function GetDataTypeName(ByVal i As Integer) As String
    Dim t As Type = GetFieldType(i)
    GetDataTypeName = Nothing
    If (t IsNot Nothing) Then GetDataTypeName = t.FullName
  End Function

  ''' <summary>
  ''' <see cref="DbDataReader.GetDateTime"/>
  ''' </summary>
  Public Overrides Function GetDateTime(ByVal i As Integer) As Date
    GetDateTime = CType(Item(i), DateTime)
  End Function

  ''' <summary>
  ''' <see cref="DbDataReader.GetDecimal"/>
  ''' </summary>
  Public Overrides Function GetDecimal(ByVal i As Integer) As Decimal
    GetDecimal = CType(Item(i), Decimal)
  End Function

  ''' <summary>
  ''' <see cref="DbDataReader.GetDouble"/>
  ''' </summary>
  Public Overrides Function GetDouble(ByVal i As Integer) As Double
    GetDouble = CType(Item(i), Double)
  End Function

  ''' <summary>
  ''' <see cref="DbDataReader.GetEnumerator"/>
  ''' </summary>
  Public Overrides Function GetEnumerator() As IEnumerator
    GetEnumerator = Data.GetEnumerator()
  End Function

  ''' <summary>
  ''' <see cref="DbDataReader.GetFieldType"/>
  ''' </summary>
  Public Overrides Function GetFieldType(ByVal i As Integer) As System.Type
    Dim dt As DataTable

    GetFieldType = Nothing
    dt = GetSchemaTable()
    If (dt.Rows.Count > i) AndAlso (i >= 0) Then
      GetFieldType = CType(dt.Rows(i)("DataType"), Type)
    End If
  End Function

  ''' <summary>
  ''' <see cref="DbDataReader.GetFloat"/>
  ''' </summary>
  Public Overrides Function GetFloat(ByVal i As Integer) As Single
    GetFloat = CType(Item(i), Single)
  End Function

  ''' <summary>
  ''' <see cref="DbDataReader.GetGuid"/>
  ''' </summary>
  Public Overrides Function GetGuid(ByVal i As Integer) As System.Guid
    Dim obj As Object = Item(i)
    If (GetType(Guid).IsInstanceOfType(obj)) Then
      GetGuid = DirectCast(obj, Guid)
    Else
      GetGuid = New Guid(CStr(obj))
    End If
  End Function

  ''' <summary>
  ''' <see cref="DbDataReader.GetInt16"/>
  ''' </summary>
  Public Overrides Function GetInt16(ByVal i As Integer) As Short
    GetInt16 = CType(Item(i), Short)
  End Function

  ''' <summary>
  ''' <see cref="DbDataReader.GetInt32"/>
  ''' </summary>
  Public Overrides Function GetInt32(ByVal i As Integer) As Integer
    GetInt32 = CType(Item(i), Integer)
  End Function

  ''' <summary>
  ''' <see cref="DbDataReader.GetInt64"/>
  ''' </summary>
  Public Overrides Function GetInt64(ByVal i As Integer) As Long
    GetInt64 = CType(Item(i), Long)
  End Function

  ''' <summary>
  ''' <see cref="DbDataReader.GetName"/>
  ''' </summary>
  Public Overrides Function GetName(ByVal i As Integer) As String
    Dim dt As DataTable

    GetName = Nothing
    dt = GetSchemaTable()
    If (dt.Rows.Count > i) AndAlso (i >= 0) Then
      GetName = CStr(dt.Rows(i)("ColumnName"))
    End If
  End Function

  ''' <summary>
  ''' <see cref="DbDataReader.GetOrdinal"/>
  ''' </summary>
  Public Overrides Function GetOrdinal(ByVal name As String) As Integer
    GetOrdinal = GetPropertyIndex(name)
  End Function

  ''' <summary>
  ''' <see cref="DbDataReader.GetString"/>
  ''' </summary>
  Public Overrides Function GetString(ByVal i As Integer) As String
    GetString = CType(Item(i), String)
  End Function

  ''' <summary>
  ''' <see cref="DbDataReader.GetValue"/>
  ''' </summary>
  Public Overrides Function GetValue(ByVal i As Integer) As Object
    GetValue = Item(i)
  End Function

  ''' <summary>
  ''' <see cref="DbDataReader.GetValues"/>
  ''' </summary>
  ''' <exception cref="ArgumentNullException">Exception occurs, when an empty <paramref name="values"/> parameter is passed</exception>
  Public Overrides Function GetValues(ByVal values() As Object) As Integer
    If (values Is Nothing) Then Throw New ArgumentNullException("values")
    GetValues = Math.Min(values.Length, FieldCount)
    For lv As Integer = 0 To GetValues - 1
      values(lv) = Item(lv)
    Next
  End Function

  ''' <summary>
  ''' <see cref="DbDataReader.HasRows"/>
  ''' </summary>
  Public Overrides ReadOnly Property HasRows As Boolean
    Get
      HasRows = (Data.Count > 0)
    End Get
  End Property

  ''' <summary>
  ''' <see cref="DbDataReader.IsDBNull"/>
  ''' </summary>
  Public Overrides Function IsDBNull(ByVal i As Integer) As Boolean
    IsDBNull = (Item(i) Is Nothing)
  End Function

  ''' <summary>
  ''' <see cref="DbDataReader.Item"/>
  ''' </summary>
  Default Public Overrides ReadOnly Property Item(ByVal i As Integer) As Object
    Get
      Dim p As PropertyData = GetProperty(i)
      If (p IsNot Nothing) Then
        Item = ConvertData(p, Data(m_Pointer).Item(GetPropertyName(i)))
      Else
        Item = Nothing
      End If
    End Get
  End Property

  ''' <summary>
  ''' <see cref="DbDataReader.Item"/>
  ''' </summary>
  Default Public Overrides ReadOnly Property Item(ByVal name As String) As Object
    Get
      Dim p As PropertyData = GetProperty(name)
      If (p IsNot Nothing) Then
        Item = ConvertData(p, Data(m_Pointer).Item(name))
      Else
        Item = Nothing
      End If
    End Get
  End Property
#End Region

#Region "Methods"
  ''' <summary>
  ''' Returns the propertydata for the named column.
  ''' </summary>
  ''' <param name="name">Name of the column</param>
  ''' <returns>PropertyData for the given name</returns>
  Private Function GetProperty(ByVal name As String) As PropertyData
    GetProperty = Nothing
    If (m_Pointer >= 0) AndAlso (m_Pointer < RecordsAffected) Then
      GetProperty = Data(m_Pointer).Properties(name)
    End If
  End Function

  ''' <summary>
  ''' Returns the propertydata for the indexed column.
  ''' </summary>
  ''' <param name="i">Index of the column</param>
  ''' <returns>PropertyData for the given column index</returns>
  Private Function GetProperty(ByVal i As Integer) As PropertyData
    Dim en As PropertyDataCollection.PropertyDataEnumerator

    GetProperty = Nothing
    If (m_Pointer >= 0) AndAlso (m_Pointer < RecordsAffected) AndAlso (i < FieldCount) Then
      en = Data(m_Pointer).Properties.GetEnumerator
      For lv As Integer = 0 To i
        en.MoveNext()
      Next
      GetProperty = en.Current
    End If
  End Function

  ''' <summary>
  ''' Gets the column name of the column index passed.
  ''' </summary>
  ''' <param name="i">Column index</param>
  ''' <returns>Column name</returns>
  Private Function GetPropertyName(ByVal i As Integer) As String
    Dim pd As PropertyData = GetProperty(i)
    GetPropertyName = Nothing
    If (pd IsNot Nothing) Then GetPropertyName = pd.Name
  End Function

  ''' <summary>
  ''' Gets the index of the column with the given name.
  ''' </summary>
  ''' <param name="name">Name of the column</param>
  ''' <returns>Index of the column or -1</returns>
  Private Function GetPropertyIndex(ByVal name As String) As Integer
    Dim i As Integer = 0
    Dim en As PropertyDataCollection.PropertyDataEnumerator

    GetPropertyIndex = -1
    If (m_Pointer >= 0) AndAlso (m_Pointer < RecordsAffected) Then
      en = Data(m_Pointer).Properties.GetEnumerator
      While en.MoveNext
        If (String.Equals(name, en.Current.Name, StringComparison.OrdinalIgnoreCase)) Then
          GetPropertyIndex = i
          Exit While
        End If
        i += 1
      End While
    End If
  End Function

  ''' <summary>
  ''' Maps a CimType to the .NET-Type.
  ''' </summary>
  ''' <param name="t">Type to map</param>
  ''' <returns>Mapped type</returns>
  Private Shared Function MapCimType(ByVal t As CimType) As Type
    Select Case t
      Case CimType.Boolean
        MapCimType = GetType(Boolean)
      Case CimType.Char16
        MapCimType = GetType(Char)
      Case CimType.DateTime
        MapCimType = GetType(DateTime)
      Case CimType.Real32
        MapCimType = GetType(Single)
      Case CimType.Real64
        MapCimType = GetType(Double)
      Case CimType.SInt16
        MapCimType = GetType(Int16)
      Case CimType.SInt32
        MapCimType = GetType(Int32)
      Case CimType.SInt64
        MapCimType = GetType(Int64)
      Case CimType.SInt8
        MapCimType = GetType(SByte)
      Case CimType.String
        MapCimType = GetType(String)
      Case CimType.UInt16
        MapCimType = GetType(UInt16)
      Case CimType.UInt32
        MapCimType = GetType(UInt32)
      Case CimType.UInt64
        MapCimType = GetType(UInt64)
      Case CimType.UInt8
        MapCimType = GetType(Byte)
      Case Else
        MapCimType = GetType(Object)
    End Select
  End Function

  ''' <summary>
  ''' Method to convert special datatypes (p.E. DateTime).
  ''' </summary>
  ''' <param name="p">PropertyData of the object</param>
  ''' <param name="value">Calue to convert/check</param>
  ''' <returns>Converted value</returns>
  Private Shared Function ConvertData(ByVal p As PropertyData, ByVal value As Object) As Object
    Dim a As Array
    Dim lv As Integer

    If (p IsNot Nothing) AndAlso (value IsNot Nothing) Then
      If (p.IsArray) Then
        a = DirectCast(value, Array)
        For Each obj As Object In a
          a.SetValue(ConvertSingleData(p, obj), lv)
          lv += 1
        Next
      Else
        value = ConvertSingleData(p, value)
      End If
    End If
    ConvertData = value
  End Function

  ''' <summary>
  ''' Converts a single (not array) object.
  ''' </summary>
  ''' <param name="p">PropertyData of the object</param>
  ''' <param name="value">Value to convert</param>
  ''' <returns>Converted value</returns>
  Private Shared Function ConvertSingleData(ByVal p As PropertyData, ByVal value As Object) As Object
    If (p IsNot Nothing) AndAlso (value IsNot Nothing) Then
      If (p.Type = CimType.DateTime) Then
        value = ManagementDateTimeConverter.ToDateTime(CStr(value))
      End If
    End If
    ConvertSingleData = value
  End Function
#End Region

#Region "IDisposable Support"
  Private disposedValue As Boolean ' So ermitteln Sie überflüssige Aufrufe

  ' IDisposable
  Protected Overrides Sub Dispose(ByVal disposing As Boolean)
    MyBase.Dispose(disposing)
    If Not Me.disposedValue Then
      If disposing Then
        ' Verwalteten Zustand löschen (verwaltete Objekte).
      End If

      ' Nicht verwaltete Ressourcen (nicht verwaltete Objekte) freigeben und Finalize() unten überschreiben.
      ' Große Felder auf NULL festlegen.
      Close()
    End If
    Me.disposedValue = True
  End Sub
#End Region
End Class
